استكشف بدائل TypeScript enum القوية مثل تأكيدات الثوابت وأنواع الاتحاد. افهم فوائدها وعيوبها وتطبيقاتها العملية لكود أنظف وأكثر قابلية للصيانة في سياق التطوير العالمي.
بدائل TypeScript Enum: التنقل في تأكيدات الثوابت وأنواع الاتحاد لكود قوي
TypeScript، وهي مجموعة فائقة قوية من JavaScript، تجلب الكتابة الثابتة إلى العالم الديناميكي لتطوير الويب. من بين ميزاتها العديدة، كان مفتاح enum دائمًا هو الخيار المفضل لتعريف مجموعة من الثوابت المسماة. توفر الـ Enums طريقة واضحة لتمثيل مجموعة ثابتة من القيم ذات الصلة، مما يعزز قابلية القراءة وأمان النوع.
ومع ذلك، مع نضوج نظام TypeScript البيئي ونمو المشاريع في التعقيد والحجم، يتساءل المطورون عالميًا بشكل متزايد عن الفائدة التقليدية للـ enums. في حين أنها مباشرة للحالات البسيطة، فإن الـ enums تقدم سلوكيات وخصائص وقت تشغيل معينة يمكن أن تؤدي أحيانًا إلى مشكلات غير متوقعة، أو تؤثر على حجم الحزمة (bundle size)، أو تعقيد تحسينات Tree-shaking. أدى هذا إلى استكشاف واسع النطاق للبدائل.
يتعمق هذا الدليل الشامل في اثنين من البدائل البارزة والفعالة للغاية لـ TypeScript enums: أنواع الاتحاد مع الحروف النصية/الرقمية (Union Types with String/Numeric Literals) و تأكيدات الثوابت (as const). سنستكشف آلياتها، وتطبيقاتها العملية، وفوائدها، ومقايضاتها، مما يزودك بالمعرفة اللازمة لاتخاذ قرارات تصميم مستنيرة لمشاريعك، بغض النظر عن حجمها أو الفريق العالمي الذي يعمل عليها. هدفنا هو تمكينك من كتابة كود TypeScript أكثر قوة وقابلية للصيانة وكفاءة.
TypeScript Enum: استعراض سريع
قبل أن نتعمق في البدائل، دعنا نراجع بإيجاز TypeScript enum التقليدي. تتيح الـ Enums للمطورين تعريف مجموعة من الثوابت المسماة، مما يجعل الكود أكثر قابلية للقراءة ويمنع تناثر "السلاسل السحرية" أو "الأرقام السحرية" في التطبيق. تأتي في شكلين أساسيين: الـ enums الرقمية والنصية.
الـ Enums الرقمية
بشكل افتراضي، تكون TypeScript enums رقمية. يتم تهيئة العضو الأول بـ 0، ويتم زيادة كل عضو لاحق تلقائيًا.
enum Direction {
Up,
Down,
Left,
Right,
}
let currentDirection: Direction = Direction.Up;
console.log(currentDirection); // Outputs: 0
console.log(Direction.Left); // Outputs: 2
يمكنك أيضًا تهيئة أعضاء الـ enum الرقمية يدويًا:
enum StatusCode {
Success = 200,
NotFound = 404,
ServerError = 500,
}
let status: StatusCode = StatusCode.NotFound;
console.log(status); // Outputs: 404
إحدى الخصائص الغريبة للـ enums الرقمية هي الربط العكسي (reverse mapping). في وقت التشغيل، يتم ترجمة الـ enum الرقمي إلى كائن JavaScript يربط الأسماء بالقيم والقيم بالأسماء.
enum UserRole {
Admin = 1,
Editor,
Viewer,
}
console.log(UserRole[1]); // Outputs: "Admin"
console.log(UserRole.Editor); // Outputs: 2
console.log(UserRole[2]); // Outputs: "Editor"
/*
Compiles to JavaScript:
var UserRole;
(function (UserRole) {
UserRole[UserRole["Admin"] = 1] = "Admin";
UserRole[UserRole["Editor"] = 2] = "Editor";
UserRole[UserRole["Viewer"] = 3] = "Viewer";
})(UserRole || (UserRole = {}));
*/
الـ Enums النصية
غالبًا ما تُفضل الـ enums النصية لقابليتها للقراءة في وقت التشغيل، لأنها لا تعتمد على الأرقام التي تتم زيادتها تلقائيًا. يجب تهيئة كل عضو بحرف نصي حرفي (literal).
enum UserPermission {
Read = "READ_PERMISSION",
Write = "WRITE_PERMISSION",
Delete = "DELETE_PERMISSION",
}
let permission: UserPermission = UserPermission.Write;
console.log(permission); // Outputs: "WRITE_PERMISSION"
الـ enums النصية لا تحصل على ربط عكسي، وهذا أمر جيد بشكل عام لتجنب سلوك وقت التشغيل غير المتوقع وتقليل إخراج JavaScript الذي تم إنشاؤه.
اعتبارات رئيسية والمزالق المحتملة للـ Enums
في حين أن الـ enums توفر الراحة، إلا أنها تأتي مع خصائص معينة تستدعي دراسة متأنية:
- كائنات وقت التشغيل: كل من الـ enums الرقمية والنصية تنشئ كائنات JavaScript في وقت التشغيل. هذا يعني أنها تساهم في حجم حزمة التطبيق الخاص بك، حتى لو كنت تستخدمها فقط للتحقق من النوع. بالنسبة للمشاريع الصغيرة، قد يكون هذا ضئيلًا، ولكن في التطبيقات واسعة النطاق مع العديد من الـ enums، يمكن أن يتراكم.
- نقص Tree-Shaking: نظرًا لأن الـ enums هي كائنات وقت تشغيل، فغالبًا ما لا يتم عمل Tree-shaking لها بفعالية بواسطة المجمعات الحديثة مثل Webpack أو Rollup. إذا قمت بتعريف enum ولكن استخدمت عضوًا واحدًا أو اثنين فقط منها، فقد يظل كائن الـ enum بأكمله مدرجًا في حزمتك النهائية. يمكن أن يؤدي هذا إلى أحجام ملفات أكبر من اللازم.
- الربط العكسي (الـ Enums الرقمية): ميزة الربط العكسي للـ enums الرقمية، في حين أنها مفيدة أحيانًا، يمكن أن تكون أيضًا مصدرًا للارتباك والسلوك غير المتوقع. إنها تضيف رمزًا إضافيًا إلى إخراج JavaScript وقد لا تكون دائمًا الوظيفة المرغوبة. على سبيل المثال، قد يؤدي تسلسل الـ enums الرقمية أحيانًا إلى تخزين الرقم فقط، والذي قد لا يكون وصفيًا مثل السلسلة النصية.
- عبء الترجمة (Transpilation Overhead): تؤدي ترجمة الـ enums إلى كائنات JavaScript إلى عبء إضافي طفيف على عملية البناء مقارنة بتعريف متغيرات الثابتة ببساطة.
- تكرار محدود: يمكن أن يكون التكرار المباشر لقيم الـ enum غير مباشر، خاصة مع الـ enums الرقمية بسبب الربط العكسي. غالبًا ما تحتاج إلى وظائف مساعدة أو حلقات محددة للحصول على القيم المطلوبة فقط.
تسلط هذه النقاط الضوء على سبب بحث العديد من فرق التطوير العالمية، خاصة تلك التي تركز على الأداء وحجم الحزمة، عن بدائل توفر أمان النوع المشابه دون البصمة وقت التشغيل أو التعقيدات الأخرى.
البديل الأول: أنواع الاتحاد مع الحروف النصية
واحدة من أبسط وأقوى البدائل للـ enums في TypeScript هي استخدام أنواع الاتحاد مع الحروف النصية أو الرقمية (Union Types with String or Numeric Literals). يعتمد هذا النهج على نظام أنواع TypeScript القوي لتعريف مجموعة من القيم المحددة والمسموح بها في وقت التصريف، دون تقديم أي هياكل جديدة في وقت التشغيل.
ما هي أنواع الاتحاد؟
يصف نوع الاتحاد قيمة يمكن أن تكون واحدة من عدة أنواع. على سبيل المثال، string | number يعني أن المتغير يمكن أن يحمل إما سلسلة نصية أو رقمًا. عند الجمع مع أنواع الحروف النصية (على سبيل المثال، "success"، 404)، يمكنك تعريف نوع يمكنه حمل مجموعة محددة فقط من القيم المعرفة مسبقًا.
مثال عملي: تعريف الحالات باستخدام أنواع الاتحاد
دعنا نفكر في سيناريو شائع: تعريف مجموعة من الحالات المحتملة لمهمة معالجة البيانات أو حساب مستخدم. باستخدام أنواع الاتحاد، يبدو هذا نظيفًا وموجزًا:
type JobStatus = "PENDING" | "IN_PROGRESS" | "COMPLETED" | "FAILED";
function processJob(status: JobStatus): void {
if (status === "COMPLETED") {
console.log("Job finished successfully.");
} else if (status === "FAILED") {
console.log("Job encountered an error.");
} else {
console.log(`Job is currently ${status}.`);
}
}
let currentJobStatus: JobStatus = "IN_PROGRESS";
processJob(currentJobStatus);
// This would result in a compile-time error:
// let invalidStatus: JobStatus = "CANCELLED"; // Error: Type '"CANCELLED"' is not assignable to type 'JobStatus'.
بالنسبة للقيم الرقمية، يكون النمط متطابقًا:
type HttpCode = 200 | 400 | 404 | 500;
function handleResponse(code: HttpCode): void {
if (code === 200) {
console.log("Operation successful.");
} else if (code === 404) {
console.log("Resource not found.");
}
}
let responseStatus: HttpCode = 200;
handleResponse(responseStatus);
لاحظ كيف نحدد type alias هنا. هذا مجرد هيكل وقت التصريف. عند الترجمة إلى JavaScript، تختفي JobStatus ببساطة، ويتم استخدام الحروف النصية/الرقمية مباشرة.
فوائد أنواع الاتحاد مع الحروف النصية
يقدم هذا النهج العديد من المزايا المقنعة:
- وقت التصريف بالكامل: يتم محو أنواع الاتحاد بالكامل أثناء التصريف. لا تنشئ أي كود JavaScript في وقت التشغيل، مما يؤدي إلى أحجام حزم أصغر وأوقات بدء تشغيل أسرع للتطبيق. هذه ميزة كبيرة للتطبيقات التي تركز على الأداء والتي يتم نشرها عالميًا حيث كل كيلوبايت يهم.
- أمان النوع الممتاز: تتحقق TypeScript بشكل صارم من التعيينات مقابل أنواع الحروف النصية المحددة، مما يوفر ضمانات قوية بأنه لا يتم استخدام إلا القيم الصالحة. هذا يمنع الأخطاء الشائعة المرتبطة بالأخطاء الإملائية أو القيم غير الصحيحة.
- Tree-Shaking الأمثل: نظرًا لعدم وجود كائن وقت تشغيل، تدعم أنواع الاتحاد بشكل جوهري Tree-shaking. لا تتضمن الحزمة الخاصة بك سوى الحروف النصية أو الرقمية الفعلية التي تستخدمها، وليس كائنًا كاملاً.
- قابلية القراءة: لمجموعة ثابتة من القيم البسيطة والمتميزة، يكون تعريف النوع واضحًا جدًا وسهل الفهم.
- البساطة: لا يتم تقديم أي هياكل لغوية جديدة أو قطع أثرية تصريف معقدة. إنها مجرد الاستفادة من ميزات نوع TypeScript الأساسية.
- الوصول المباشر إلى القيمة: تتعامل مباشرة مع قيم السلاسل النصية أو الأرقام، مما يبسط التسلسل وإلغاء التسلسل، خاصة عند التفاعل مع واجهات برمجة التطبيقات (APIs) أو قواعد البيانات التي تتوقع معرفات نصية محددة.
عيوب أنواع الاتحاد مع الحروف النصية
على الرغم من قوتها، فإن أنواع الاتحاد لها أيضًا بعض القيود:
- التكرار للبيانات المرتبطة: إذا كنت بحاجة إلى ربط بيانات إضافية أو بيانات وصفية بكل عضو "enum" (على سبيل المثال، تسمية عرض، أيقونة، لون)، فلا يمكنك القيام بذلك مباشرة داخل تعريف نوع الاتحاد. ستحتاج عادةً إلى كائن تخطيط منفصل.
- لا يوجد تكرار مباشر لجميع القيم: لا توجد طريقة مدمجة للحصول على مصفوفة من جميع القيم الممكنة من نوع الاتحاد في وقت التشغيل. على سبيل المثال، لا يمكنك بسهولة الحصول على
["PENDING", "IN_PROGRESS", "COMPLETED", "FAILED"]مباشرة منJobStatus. هذا غالبًا ما يستلزم الاحتفاظ بمصفوفة منفصلة للقيم إذا كنت بحاجة إلى عرضها في واجهة مستخدم (على سبيل المثال، قائمة منسدلة). - أقل مركزية: إذا كانت مجموعة القيم مطلوبة كنوع وكصفيف من قيم وقت التشغيل، فقد تجد نفسك تحدد القائمة مرتين (مرة كنوع، ومرة كمصفوفة وقت تشغيل)، مما قد يؤدي إلى إمكانية عدم التزامن.
على الرغم من هذه العيوب، بالنسبة للعديد من السيناريوهات، توفر أنواع الاتحاد حلاً نظيفًا وفعالًا وآمنًا من حيث النوع يتوافق جيدًا مع ممارسات تطوير JavaScript الحديثة.
البديل الثاني: تأكيدات الثوابت (as const)
يعد تأكيد as const، الذي تم تقديمه في TypeScript 3.4، أداة قوية أخرى تقدم بديلاً ممتازًا للـ enums، خاصة عندما تحتاج إلى كائن وقت تشغيل واستنتاج قوي للنوع. يسمح لـ TypeScript باستنتاج أضيق نوع ممكن للتعبيرات الحرفية.
ما هي تأكيدات الثوابت؟
عند تطبيق as const على متغير أو مصفوفة أو حرف كائن، يعامل TypeScript جميع الخصائص داخل هذا الحرف على أنها readonly ويستنتج أنواع الحروف النصية الخاصة بها بدلاً من الأنواع الأوسع (على سبيل المثال، "foo" بدلاً من string، 123 بدلاً من number). هذا يجعل من الممكن اشتقاق أنواع اتحاد محددة للغاية من هياكل بيانات وقت التشغيل.
مثال عملي: إنشاء كائن "شبه Enum" مع as const
دعنا نعد إلى مثال حالة المهمة الخاص بنا. مع as const، يمكننا تحديد مصدر واحد للحقيقة لحالاتنا، والذي يعمل ككائن وقت تشغيل وكأساس لتعريفات الأنواع.
const JobStatuses = {
PENDING: "PENDING",
IN_PROGRESS: "IN_PROGRESS",
COMPLETED: "COMPLETED",
FAILED: "FAILED",
} as const;
// JobStatuses.PENDING is now inferred as type "PENDING" (not just string)
// JobStatuses is inferred as type {
// readonly PENDING: "PENDING";
// readonly IN_PROGRESS: "IN_PROGRESS";
// readonly COMPLETED: "COMPLETED";
// readonly FAILED: "FAILED";
// }
في هذه المرحلة، JobStatuses هو كائن JavaScript في وقت التشغيل، تمامًا مثل enum العادي. ومع ذلك، فإن استنتاج النوع الخاص به أكثر دقة بكثير.
الجمع مع typeof و keyof لأنواع الاتحاد
تظهر القوة الحقيقية عند الجمع بين as const وعوامل typeof و keyof في TypeScript لاشتقاق نوع اتحاد من قيم الكائن أو مفاتيحه.
const JobStatuses = {
PENDING: "PENDING",
IN_PROGRESS: "IN_PROGRESS",
COMPLETED: "COMPLETED",
FAILED: "FAILED",
} as const;
// Type representing the keys (e.g., "PENDING" | "IN_PROGRESS" | ...)
type JobStatusKeys = keyof typeof JobStatuses;
// Type representing the values (e.g., "PENDING" | "IN_PROGRESS" | ...)
type JobStatusValues = typeof JobStatuses[keyof typeof JobStatuses];
function processJobWithConstAssertion(status: JobStatusValues): void {
if (status === JobStatuses.COMPLETED) {
console.log("Job finished successfully.");
} else if (status === JobStatuses.FAILED) {
console.log("Job encountered an error.");
} else {
console.log(`Job is currently ${status}.`);
}
}
let currentJobStatusFromObject: JobStatusValues = JobStatuses.IN_PROGRESS;
processJobWithConstAssertion(currentJobStatusFromObject);
// This would result in a compile-time error:
// let invalidStatusFromObject: JobStatusValues = "CANCELLED"; // Error!
يوفر هذا النمط أفضل ما في العالمين: كائن وقت تشغيل للتكرار أو الوصول المباشر إلى الخصائص، ونوع اتحاد وقت التصريف للتحقق الصارم من النوع.
فوائد تأكيدات الثوابت مع أنواع الاتحاد المشتقة
- مصدر واحد للحقيقة: تحدد ثوابتك مرة واحدة في كائن JavaScript عادي، وتشتق منه الوصول في وقت التشغيل وأنواع وقت التصريف. هذا يقلل بشكل كبير من التكرار ويحسن قابلية الصيانة عبر فرق التطوير المتنوعة.
- أمان النوع: على غرار أنواع الاتحاد النقية، تحصل على أمان نوع ممتاز، مما يضمن استخدام القيم المحددة مسبقًا فقط.
- قابلية التكرار في وقت التشغيل: نظرًا لأن
JobStatusesهو كائن JavaScript عادي، يمكنك بسهولة التكرار فوق مفاتيحه أو قيمه باستخدام طرق JavaScript القياسية مثلObject.keys()أوObject.values()أوObject.entries(). هذا لا يقدر بثمن لواجهات المستخدم الديناميكية (على سبيل المثال، ملء القوائم المنسدلة) أو التسجيل. - البيانات المرتبطة: يدعم هذا النمط بشكل طبيعي ربط بيانات إضافية بكل عضو "enum".
- إمكانية Tree-Shaking أفضل (مقارنة بالـ Enums): في حين أن
as constينشئ كائن وقت تشغيل، فهو كائن JavaScript قياسي. المجمعات الحديثة تكون عمومًا أكثر فعالية في Tree-shaking الخصائص غير المستخدمة أو حتى الكائنات بأكملها إذا لم يتم الرجوع إليها، مقارنة بإخراج الترجمة لـ TypeScript enum. ومع ذلك، إذا كان الكائن كبيرًا وتم استخدام عدد قليل فقط من الخصائص، فقد يظل الكائن بأكمله مدرجًا إذا تم استيراده بطريقة تمنع Tree-shaking الدقيق. - المرونة: يمكنك تعريف قيم ليست مجرد سلاسل نصية أو أرقام بل كائنات أكثر تعقيدًا إذا لزم الأمر، مما يجعل هذا النمط مرنًا للغاية.
const FileOperations = {
UPLOAD: {
label: "Upload File",
icon: "upload-icon.svg",
permission: "can_upload"
},
DOWNLOAD: {
label: "Download File",
icon: "download-icon.svg",
permission: "can_download"
},
DELETE: {
label: "Delete File",
icon: "delete-icon.svg",
permission: "can_delete"
},
} as const;
type FileOperationType = keyof typeof FileOperations; // "UPLOAD" | "DOWNLOAD" | "DELETE"
type FileOperationDetail = typeof FileOperations[keyof typeof FileOperations]; // { label: string; icon: string; permission: string; }
function performOperation(opType: FileOperationType) {
const details = FileOperations[opType];
console.log(`Performing: ${details.label} (Permission: ${details.permission})`);
}
performOperation("UPLOAD");
عيوب تأكيدات الثوابت
- وجود كائن وقت التشغيل: على عكس أنواع الاتحاد النقية، لا يزال هذا النهج ينشئ كائن JavaScript في وقت التشغيل. في حين أنه كائن قياسي وغالبًا ما يكون أفضل لـ Tree-shaking من كائنات enum الخاصة بـ TypeScript، إلا أنه ليس محوًا بالكامل.
- تعريف نوع أكثر إيجازًا قليلاً: يتطلب اشتقاق نوع الاتحاد (
keyof typeof ...أوtypeof ...[keyof typeof ...]) قدرًا إضافيًا من بناء الجملة مقارنة بمجرد سرد الحروف النصية لنوع الاتحاد. - إمكانية سوء الاستخدام: إذا لم يتم استخدامه بعناية، فقد يساهم كائن
as constكبير جدًا في حجم الحزمة بشكل كبير إذا لم يتم Tree-shaking محتوياته بفعالية عبر حدود الوحدة.
بالنسبة للسيناريوهات التي تحتاج فيها إلى كل من أمان النوع وقت التصريف ومجموعة وقت التشغيل من القيم التي يمكن تكرارها أو توفير بيانات مرتبطة بها، فإن as const غالبًا ما يكون الخيار المفضل بين مطوري TypeScript في جميع أنحاء العالم.
مقارنة البدائل: متى تستخدم ماذا؟
يعتمد الاختيار بين أنواع الاتحاد وتأكيدات الثوابت إلى حد كبير على متطلباتك المحددة فيما يتعلق بوجود وقت التشغيل، وقابلية التكرار، وما إذا كنت بحاجة إلى ربط بيانات إضافية بثوابتك. دعنا نكسر عوامل اتخاذ القرار.
البساطة مقابل القوة
- أنواع الاتحاد: توفر أقصى قدر من البساطة عندما تحتاج فقط إلى مجموعة آمنة من أنواع النوع من السلاسل النصية أو الرقمية في وقت التصريف. إنها الخيار الأخف وزناً.
- تأكيدات الثوابت: توفر نمطًا أكثر قوة عندما تحتاج إلى كل من أمان النوع وقت التصريف وكائن وقت تشغيل يمكن الاستعلام عنه أو تكراره أو توسيعه ببيانات وصفية إضافية. الإعداد الأولي أكثر إيجازًا قليلاً، ولكنه يؤتي ثماره في الميزات.
وجود وقت التشغيل مقابل وقت التصريف
- أنواع الاتحاد: هي هياكل وقت تصريف بالكامل. لا تنشئ أي كود JavaScript على الإطلاق. هذا مثالي للتطبيقات التي يكون فيها تقليل حجم الحزمة أمرًا بالغ الأهمية، والقيم نفسها كافية دون الحاجة إلى الوصول إليها ككائن في وقت التشغيل.
- تأكيدات الثوابت: تنشئ كائن JavaScript عادي في وقت التشغيل. هذا الكائن قابل للوصول وقابل للاستخدام في كود JavaScript الخاص بك. في حين أنه يضيف إلى حجم الحزمة، إلا أنه أكثر كفاءة بشكل عام من TypeScript enums وهو مرشح أفضل لـ Tree-shaking.
متطلبات قابلية التكرار
- أنواع الاتحاد: لا توفر طريقة مباشرة للتكرار فوق جميع القيم الممكنة في وقت التشغيل. إذا كنت بحاجة إلى ملء قائمة منسدلة أو عرض جميع الخيارات، فسيتعين عليك تعريف مصفوفة منفصلة لهذه القيم، مما قد يؤدي إلى تكرار.
- تأكيدات الثوابت: تتفوق هنا. نظرًا لأنك تتعامل مع كائن JavaScript قياسي، يمكنك بسهولة استخدام
Object.keys()أوObject.values()أوObject.entries()للحصول على مصفوفة من المفاتيح أو القيم أو أزواج المفاتيح والقيم، على التوالي. هذا يجعلها مثالية لواجهات المستخدم الديناميكية أو أي سيناريو يتطلب تعداد وقت التشغيل.
const PaymentMethods = {
CREDIT_CARD: "Credit Card",
PAYPAL: "PayPal",
BANK_TRANSFER: "Bank Transfer",
} as const;
type PaymentMethodType = keyof typeof PaymentMethods;
// Get all keys (e.g., for internal logic)
const methodKeys = Object.keys(PaymentMethods) as PaymentMethodType[];
console.log(methodKeys); // ["CREDIT_CARD", "PAYPAL", "BANK_TRANSFER"]
// Get all values (e.g., for display in a dropdown)
const methodLabels = Object.values(PaymentMethods);
console.log(methodLabels); // ["Credit Card", "PayPal", "Bank Transfer"]
// Get key-value pairs (e.g., for mapping)
const methodEntries = Object.entries(PaymentMethods);
console.log(methodEntries); // [["CREDIT_CARD", "Credit Card"], ...]
آثار Tree-Shaking
- أنواع الاتحاد: قابلة للـ Tree-shaking بطبيعتها لأنها وقت تصريف فقط.
- تأكيدات الثوابت: في حين أنها تنشئ كائن وقت تشغيل، يمكن للمجمعات الحديثة غالبًا عمل Tree-shaking للخصائص غير المستخدمة لهذا الكائن بشكل أكثر فعالية من كائنات enum التي تم إنشاؤها بواسطة TypeScript. ومع ذلك، إذا تم استيراد الكائن بأكمله وتمت الإشارة إليه، فمن المحتمل أن يتم تضمينه. يمكن أن يساعد تصميم الوحدة النمطية الحذر.
أفضل الممارسات والمقاربات الهجينة
ليس دائمًا "إما/أو" الوضع. غالبًا ما يتضمن الحل الأفضل نهجًا هجينًا، خاصة في التطبيقات الكبيرة والدولية:
- للأعلام البسيطة، الداخلية فقط، التي لا تحتاج أبدًا إلى التكرار أو لديها بيانات مرتبطة، فإن أنواع الاتحاد هي عمومًا الخيار الأكثر أداءً ونظافة.
- لمجموعات الثوابت التي تحتاج إلى التكرار، أو عرضها في واجهات المستخدم، أو لديها بيانات وصفية غنية مرتبطة بها (مثل التسميات، الأيقونات، أو الأذونات)، فإن نمط تأكيدات الثوابت هو الأفضل.
- الجمع بين قابلية القراءة والتدويل: تستخدم العديد من الفرق
as constللمعرفات الداخلية، ثم تشتق تسميات العرض المحلية من نظام تدويل (i18n) منفصل.
// src/constants/order-status.ts
const OrderStatuses = {
PENDING: "PENDING",
PROCESSING: "PROCESSING",
SHIPPED: "SHIPPED",
DELIVERED: "DELIVERED",
CANCELLED: "CANCELLED",
} as const;
type OrderStatus = typeof OrderStatuses[keyof typeof OrderStatuses];
export { OrderStatuses, type OrderStatus };
// src/i18n/en.json
{
"orderStatus": {
"PENDING": "Pending Confirmation",
"PROCESSING": "Processing Order",
"SHIPPED": "Shipped",
"DELIVERED": "Delivered",
"CANCELLED": "Cancelled"
}
}
// src/components/OrderStatusDisplay.tsx
import { OrderStatuses, type OrderStatus } from "../constants/order-status";
import { useTranslation } from "react-i18next"; // Example i18n library
interface OrderStatusDisplayProps {
status: OrderStatus;
}
function OrderStatusDisplay({ status }: OrderStatusDisplayProps) {
const { t } = useTranslation();
const displayLabel = t(`orderStatus.${status}`);
return Status: {displayLabel};
}
// Usage:
//
يستفيد هذا النهج الهجين من أمان النوع وقابلية التكرار وقت التشغيل لـ as const مع إبقاء سلاسل العرض المحلية منفصلة وسهلة الإدارة، وهو اعتبار حاسم للتطبيقات العالمية.
أنماط واعتبارات متقدمة
إلى جانب الاستخدام الأساسي، يمكن دمج كل من أنواع الاتحاد وتأكيدات الثوابت في أنماط أكثر تطوراً لزيادة تحسين جودة الكود وقابلية الصيانة.
استخدام حراس النوع مع أنواع الاتحاد
عند العمل مع أنواع الاتحاد، خاصة عندما يشمل الاتحاد أنواعًا متنوعة (ليس فقط الحروف النصية)، تصبح حراس النوع ضرورية لتضييق الأنواع. مع أنواع الاتحاد الحرفية، تقدم الاتحادات المميزة قوة هائلة.
type SuccessEvent = { type: "SUCCESS"; data: any; };
type ErrorEvent = { type: "ERROR"; message: string; code: number; };
type SystemEvent = SuccessEvent | ErrorEvent;
function handleSystemEvent(event: SystemEvent) {
if (event.type === "SUCCESS") {
console.log("Data received:", event.data);
// event is now narrowed to SuccessEvent
} else {
console.log("Error occurred:", event.message, "Code:", event.code);
// event is now narrowed to ErrorEvent
}
}
handleSystemEvent({ type: "SUCCESS", data: { user: "Alice" } });
handleSystemEvent({ type: "ERROR", message: "Network failure", code: 503 });
هذا النمط، الذي يطلق عليه غالبًا "الاتحادات المميزة" (discriminated unions)، قوي للغاية وآمن من حيث النوع، ويوفر ضمانات وقت التصريف حول هيكل بياناتك بناءً على خاصية حرفية مشتركة (المميز).
Object.values() مع as const وتأكيدات النوع
عند استخدام نمط as const، يمكن أن يكون Object.values() مفيدًا جدًا. ومع ذلك، قد يكون الاستنتاج الافتراضي لـ TypeScript لـ Object.values() أوسع من المرغوب فيه (على سبيل المثال، string[] بدلاً من اتحاد محدد من الحروف النصية). قد تحتاج إلى تأكيد نوع لصرامته.
const Statuses = {
ACTIVE: "Active",
INACTIVE: "Inactive",
PENDING: "Pending",
} as const;
type StatusValue = typeof Statuses[keyof typeof Statuses]; // "Active" | "Inactive" | "Pending"
// Object.values(Statuses) is inferred as (string | "Active" | "Inactive" | "Pending")[]
// We can assert it more narrowly if needed:
const allStatusValues: StatusValue[] = Object.values(Statuses);
console.log(allStatusValues); // ["Active", "Inactive", "Pending"]
// For a dropdown, you might pair values with labels if they differ
const statusOptions = Object.entries(Statuses).map(([key, value]) => ({
value: key, // Use the key as the actual identifier
label: value // Use the value as the display label
}));
console.log(statusOptions);
/*
[
{ value: "ACTIVE", label: "Active" },
{ value: "INACTIVE", label: "Inactive" },
{ value: "PENDING", label: "Pending" }
]
*/
يوضح هذا كيفية الحصول على مصفوفة قوية من القيم مناسبة لعناصر واجهة المستخدم مع الاحتفاظ بأنواع الحروف النصية.
التدويل (i18n) والتسميات المحلية
بالنسبة للتطبيقات العالمية، تعد إدارة السلاسل النصية المحلية أمرًا بالغ الأهمية. في حين أن TypeScript enums وبدائلها توفر معرفات داخلية، غالبًا ما تحتاج تسميات العرض إلى فصل لـ i18n. يكمل نمط as const أنظمة i18n بشكل جميل.
تقوم بتعريف المعرفات الثابتة وغير القابلة للتغيير الخاصة بك باستخدام as const. هذه المعرفات متسقة عبر جميع اللغات وتعمل كمفاتيح لملفات الترجمة الخاصة بك. يتم بعد ذلك جلب تسميات العرض الفعلية من مكتبة i18n (على سبيل المثال، react-i18next، vue-i18n، FormatJS) بناءً على اللغة المحددة للمستخدم.
// app/features/product/constants.ts
export const ProductCategories = {
ELECTRONICS: "ELECTRONICS",
APPAREL: "APPAREL",
HOME_GOODS: "HOME_GOODS",
BOOKS: "BOOKS",
} as const;
export type ProductCategory = typeof ProductCategories[keyof typeof ProductCategories];
// app/i18n/locales/en.json
{
"productCategories": {
"ELECTRONICS": "Electronics",
"APPAREL": "Apparel & Accessories",
"HOME_GOODS": "Home Goods",
"BOOKS": "Books"
}
}
// app/i18n/locales/es.json
{
"productCategories": {
"ELECTRONICS": "Electrónica",
"APPAREL": "Ropa y Accesorios",
"HOME_GOODS": "Artículos para el hogar",
"BOOKS": "Libros"
}
}
// app/components/ProductCategorySelector.tsx
import { ProductCategories, type ProductCategory } from "../features/product/constants";
import { useTranslation } from "react-i18next";
function ProductCategorySelector() {
const { t } = useTranslation();
return (
);
}
يعد فصل الاهتمامات هذا أمرًا بالغ الأهمية للتطبيقات العالمية القابلة للتطوير. تضمن أنواع TypeScript استخدام المفاتيح الصالحة دائمًا، ويتعامل نظام i18n مع طبقة العرض بناءً على لغة المستخدم. هذا يتجنب وجود سلاسل نصية معتمدة على اللغة مضمنة مباشرة داخل منطق التطبيق الأساسي الخاص بك، وهو نمط شائع مناهض للتطوير للفرق العالمية.
الخلاصة: تمكين خيارات تصميم TypeScript الخاصة بك
بينما تستمر TypeScript في التطور وتمكين المطورين في جميع أنحاء العالم من بناء تطبيقات أكثر قوة وقابلية للتطوير، يصبح فهم ميزاتها الدقيقة وبدائلها مهمًا بشكل متزايد. في حين أن مفتاح enum الخاص بـ TypeScript يوفر طريقة مريحة لتعريف الثوابت المسماة، فإن بصمته وقت التشغيل، وقيود Tree-shaking، وتعقيدات الربط العكسي غالبًا ما تجعل البدائل الحديثة أكثر جاذبية للمشاريع الحساسة للأداء أو واسعة النطاق.
أنواع الاتحاد مع الحروف النصية/الرقمية تبرز كالحل الأكثر نحافة وتركيزًا على وقت التصريف. إنها توفر أمانًا لا هوادة فيه للنوع دون إنشاء أي JavaScript في وقت التشغيل، مما يجعلها مثالية للسيناريوهات التي يكون فيها الحد الأدنى لحجم الحزمة وأقصى قدر من Tree-shaking أولوية، ولا يكون تعداد وقت التشغيل مصدر قلق.
من ناحية أخرى، فإن تأكيدات الثوابت (as const) مقترنة بـ typeof و keyof تقدم نمطًا مرنًا وقويًا للغاية. إنها توفر مصدرًا واحدًا للحقيقة لثوابتك، وأمانًا قويًا للنوع وقت التصريف، والقدرة الحاسمة على التكرار فوق القيم في وقت التشغيل. هذا النهج مناسب بشكل خاص للحالات التي تحتاج فيها إلى ربط بيانات إضافية بثوابتك، أو ملء واجهات المستخدم الديناميكية، أو التكامل بسلاسة مع أنظمة التدويل.
من خلال النظر بعناية في المقايضات – بصمة وقت التشغيل، واحتياجات قابلية التكرار، وتعقيد البيانات المرتبطة – يمكنك اتخاذ قرارات مستنيرة تؤدي إلى كود TypeScript أنظف وأكثر كفاءة وقابلية للصيانة. إن تبني هذه البدائل ليس مجرد كتابة TypeScript "حديث"؛ بل هو اتخاذ خيارات معمارية متعمدة تعزز أداء تطبيقك، وتجربة المطور، والاستدامة طويلة الأجل لجمهور عالمي.
قم بتمكين تطوير TypeScript الخاص بك عن طريق اختيار الأداة المناسبة للمهمة المناسبة، والانتقال إلى ما بعد enum الافتراضي عندما توجد بدائل أفضل.